#ifndef PARAFLUIDATOMVOXELIZER_H
#define PARAFLUIDATOMVOXELIZER_H

#ifndef MIN_SLOTS_PER_BUCKET
#define MIN_SLOTS_PER_BUCKET 1
#endif

#include "parafluidatom.h"
#include "parafluidvector.h"

#include <QDataStream>
#include <QVector>

namespace ParaFluid
{
	
	/**
	* Atom Voxelizer
	*/
	class AtomVoxelizer
	{
		
	public:
		
		/**
		* Read-Write Visitor
		*/
		class ReadWriteVisitor
		{
			
		public:
			
			/**
			* Destructor
			*/
			virtual ~ReadWriteVisitor() { /* does nothing */ }
			
			/**
			* Visit an Atom
			*/
			virtual void visit(Atom & atom) = 0;
			
		};
		
		/**
		* Read-Only Visitor
		*/
		class ReadOnlyVisitor
		{
			
		public:
			
			/**
			* Destructor
			*/
			virtual ~ReadOnlyVisitor() { /* does nothing */ }
			
			/**
			* Visit an Atom
			*/
			virtual void visit(const Atom & atom) = 0;
			
		};
		
		/**
		* Bucket
		* An Hybrid Bucket that hold a pre-defined number of Slots
		* and all the extra Slots in a Vector
		*/
		class Bucket
		{
		
		public:
		
			/**
			* Destructor
			*/
			~Bucket();
		
			/**
			* Default Constructor
			*/
			Bucket();
		
			/**
			* Copy Constructor
			*/
			Bucket(const Bucket & other);
		
			/**
			* Assignment Operator
			*/
			Bucket & operator= (const Bucket & rhs);
		
			/**
			* Serialize
			*/
			void serialize(QDataStream & stream) const;
			
			/**
			* Unserialize
			*/
			void unserialize(QDataStream & stream);
			
			/**
			* Getter for the number of Occupied Slots
			*/
			int occupiedSlots() const;
		
			/**
			* Put the specified Atom in the next Slot
			* @complexity Amortized O(1)
			*/
			void append(const Atom & atom);
		
			/**
			* Remove the specified Atom from all the Slots
			* @complexity O(n)
			*/
			void remove(const Atom & atom);
		
			/**
			* Remove the specified Slot
			* @complexity O(n)
			*/
			void removeReserved(int slot);
			
			/**
			* Remove the specified Slot
			* @complexity O(n)
			*/
			void removeExtra(int slot);
			
			/**
			* Remove all Atoms from all Slots
			* @complexity O(n)
			*/
			void clear();
		
			/**
			* Accept Visitor for all Atoms of all Slots
			*/
			void acceptVisitor(ReadWriteVisitor & visitor);
		
			/**
			* Accept Visitor for all Atoms of all Slots
			*/
			void acceptVisitor(ReadOnlyVisitor & visitor) const;
		
			/**
			* Accept Visitor for all Atoms specified by their indicies
			*/
			void acceptVisitor(ReadWriteVisitor & visitor, int indexFirst, int indexLast);
			
		private:
			
			/**
			* Number of Reserved Slots that are Occupied by an Atom
			*/
			int _reservedSlotsOccupied;
			
			/**
			* Reserved Slots
			*/
			Atom _reservedSlots[MIN_SLOTS_PER_BUCKET];
			
			/**
			* Extra Slots
			*/
			QVector<Atom> _extraSlots;
			
		};
		
		/**
		* Destructor
		*/
		~AtomVoxelizer();
	
		/**
		* Default Constructor
		*/
		AtomVoxelizer();
	
		/**
		* Constructor with specified number of Buckets
		*/
		AtomVoxelizer(int buckets, float smoothingDistance);
	
		/**
		* Copy Constructor
		*/
		AtomVoxelizer(const AtomVoxelizer & other);
	
		/**
		* Assignment Operator
		*/
		AtomVoxelizer & operator= (const AtomVoxelizer & rhs);
		
		/**
		* Serialize
		*/
		void serialize(QDataStream & stream) const;
		
		/**
		* Unserialize
		*/
		void unserialize(QDataStream & stream);
		
		/**
		* Getter for Smoothing Distance
		*/
		const float & smoothingDistance() const;
		
		/**
		* Setter for Smoothing Distance
		*/
		void setSmoothingDistance(const float & value);
		
		/**
		* Getter for the Number of Atoms
		*/
		int count() const;
		
		/**
		* Insert the specified Atom
		*/
		void insert(const Atom & atom);
	
		/**
		* Remove the specified Atom
		*/
		void remove(const Atom & atom);
	
		/**
		* Remove all Atoms
		*/
		void clear();
		
		/**
		* When the Atoms Position have changed, call this method to "Re-hash" their position
		* so that they'll be put in the proper voxel.
		*/
		void rehash();
		
		/**
		* Accept Visitor for all Atoms
		*/
		void acceptVisitor(ReadWriteVisitor & visitor);
	
		/**
		* Accept Visitor for all Atoms
		*/
		void acceptVisitor(ReadOnlyVisitor & visitor) const;
	
		/**
		* Accept Visitor for Atoms in the Voxel at the specified Position
		*/
		void acceptVisitor(ReadWriteVisitor & visitor, const Vector & position);
	
		/**
		* Accept Visitor for Atoms in the Voxel at the specified Position
		*/
		void acceptVisitor(ReadOnlyVisitor & visitor, const Vector & position) const;
	
		/**
		* Accept Visitor for Atoms at the specified indices
		*/
		void acceptVisitor(ReadWriteVisitor & visitor, int indexFirst, int indexLast);
		
		/**
		* Voxelize a Position with Smoothing Distance
		*/
		Vector voxelize(const Vector & position) const;
		
	private:
		
		/**
		* Hash Function
		*/
		unsigned int hash(const Vector & voxel) const;
		
		/**
		* Smoothing Distance
		*/
		float _smoothingDistance;
		
		/**
		* Buckets
		*/
		QVector<Bucket> _buckets;
		
		/**
		* Count
		*/
		int _count;
		
	};
	
}

QDataStream & operator<< (QDataStream & stream, const ParaFluid::AtomVoxelizer & atomVoxelizer);

QDataStream & operator>> (QDataStream & stream, ParaFluid::AtomVoxelizer & atomVoxelizer);

#endif

